/* 通过配置文件加载handler */
const fs = require('fs')
const path = require('path')
const yaml = require('js-yaml')
const pry = require('pryjs')
const logger = require('./Logger')
const HandlerBuilder = require('./HandlerBuilder')

class HandlerResolver {
  constructor (handlerBuilder = HandlerBuilder) {
    this._store = {}
    this._configDirectoryPath = 'config'
    this._handlerBuilder = handlerBuilder
    this._load()
  }

  _load () {
    if (fs.existsSync(this._configDirectoryPath)) {
      this._loadFromDirectory('/', [])
    }
  }

  /* relativeDirectoryPath相对于this._configDirectoryPath，以'/'开始 */
  _loadFromDirectory(relativeDirectoryPath, _defaultConfigs) {
    const fullDirectoryPath = path.join(this._configDirectoryPath, relativeDirectoryPath)
    const files = fs.readdirSync(fullDirectoryPath)
    const defaultConfigs = this._loadDefaultConfigs(relativeDirectoryPath, _defaultConfigs)
    files.forEach(file => {
      if (file === 'default.yml') return
      const relativeFilePath = path.join(relativeDirectoryPath, file)
      const fullFilePath = path.join(this._configDirectoryPath, relativeFilePath)
      const stat = fs.lstatSync(fullFilePath)
      if (stat.isDirectory()) {
        this._loadFromDirectory(relativeFilePath, defaultConfigs)
      } else if (stat.isFile()) {
        if (file.endsWith('.yml')) {
          this._loadFromFile(relativeFilePath, defaultConfigs)
        } else {
          logger.info('跳过加载配置"%s"，只加载后缀为.yml的配置文件。', fullFilePath)
        }
      } else {
        logger.warn('跳过加载配置"%s"，不是文件或文件夹。', fullFilePath)
      }
    })
  }

  /* 从配置文件加载handlers；如果YAML文件格式错误，则handlers数组仅包含一个handler，它匹配所有请求并返回YAML格式错误的response.
   * relativeFilePath相对于this._configDirectoryPath，以'/'开始
   */
  _loadFromFile(relativeFilePath, defaultConfigs) {
    const configPath = relativeFilePath.replace(/\.[^/.]+$/, "")
    const fullFilePath = path.join(this._configDirectoryPath, relativeFilePath)
    try {
      const doc = loadYAML(fullFilePath)
      logger.info('成功加载配置文件"%s"。', fullFilePath)
      const handlers = []
      for (let key in doc) {
        if (key === 'default') continue
        let handler = this._handlerBuilder(...defaultConfigs, doc['default'], doc[key])
        handlers.push(handler)
      }
      if (handlers.length > 0) {
        this._store[configPath] = handlers
      }
    } catch (err) {
      if (err instanceof yaml.YAMLException) {
        logger.error('加载配置文件"%s"失败，出现YAML语法错误。详情：\n%s', fullFilePath, err.message)
        this._store[configPath] = [new YAMLErrorHandler(err.message)]
      } else {
        throw err
      }
    }
  }

  _loadDefaultConfigs(relativeDirectoryPath, _defaultConfigs) {
    const defaultFilePath = path.join(this._configDirectoryPath, relativeDirectoryPath, 'default.yml')
    try {
      if(fs.existsSync(defaultFilePath) && fs.lstatSync(defaultFilePath).isFile()) {
        const doc = loadYAML(defaultFilePath)
        logger.info('成功加载默认配置"%s"。', defaultFilePath)
        return [..._defaultConfigs, doc]
      } else {
        return _defaultConfigs
      }
    } catch (err) {
      if (err instanceof yaml.YAMLException) {
        logger.error('加载默认配置"%s"失败，出现YAML语法错误。详情：\n%s', defaultFilePath, err.message)
        return _defaultConfigs
      } else {
        throw err
      }
    }
  }

  resolve (path, json) {
    const handlers = this._store[path]
    if (!handlers) {
      throw new HandlerResolver.ResolveError('PATH_NOT_RESOLVED')
    }
    const handler = handlers.find(handler => handler.match(json))
    if (!handler) {
      throw new HandlerResolver.ResolveError('HANDLER_NOT_RESOLVED')
    }
    return handler
  }
}

HandlerResolver.ResolveError = class HandlerResolver extends Error {
  constructor (code) {
    super()
    this.code = code
  }
}

function loadYAML (path) {
  const content = fs.readFileSync(path)
  return yaml.safeLoad(content)
}

function indentMultipleLines (text, n = 0) {
  const spaces = ' '.repeat(n)
  return text.split('\n').map(line => `${spaces}${line}`).join('\n')
}

class YAMLErrorHandler {
  constructor (reason) {
    this._message = `配置文件的YMAL语法错误，详情：\n${indentMultipleLines(reason, 2)}`
  }

  match () { return true }

  handle () {
    return { code: 'config_yaml_error', message: this._message }
  }
}

module.exports = HandlerResolver
